using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Microsoft.Practices.Composite.Regions;
using PrismNavigationFramework.Infrastructure;
using PrismNavigationFramework.MVVM;

namespace PrismNavigationFramework.Navigation
{

    public class PrismViewLoadingService : IPrismViewLoadingService
    {
        private class ModuleViewLoadingInformation
        {
            public string ModuleName { get; set; }
            public string ModuleUrlFriendlyName { get; set; }
            public string ViewName { get; set; }
            public string Path { get; set; }
            public string RegionName { get; set; }
        }

        private Queue<ModuleViewLoadingInformation> _queueOfViewsToDownloadWhenTheirModuleIsDownloaded = new Queue<ModuleViewLoadingInformation>();

        private readonly IPrismNavigationConfiguration _prismNavigationConfiguration;
        private readonly IRegionManager _regionManager;
        private readonly IViewCreationService _viewCreationService;
        private readonly IViewToUrlMappingRegistrationService _viewToUrlMappingService;
        private readonly IViewNavigationContextInformationParsingService _viewNavigationInformationParsingService;
        private readonly IPrismModuleLoadingService _moduleLoadingService;
        private readonly IModuleNamesRegistrationService _moduleNamesRegistrationService;
        private readonly IViewInitializationService _viewInitializationService;
        private readonly IKeyValuePairsParsingService _keyValuePairsParsingService;

        private string _currentMainRegionUrl = string.Empty;
        public string CurrentMainRegionUrl
        {
            get { return _currentMainRegionUrl; }
            set { _currentMainRegionUrl = value; }
        }

        public PrismViewLoadingService(
            IPrismNavigationConfiguration prismNavigationConfiguration,
            IRegionManager regionManager, 
            IViewCreationService viewCreationService, 
            IViewToUrlMappingRegistrationService viewToUrlMappingService, 
            IViewNavigationContextInformationParsingService viewNavigationInformationParsingService, 
            IPrismModuleLoadingService moduleLoadingService,
            IModuleNamesRegistrationService moduleNamesRegistrationService,
            IViewInitializationService viewInitializationService,
            IKeyValuePairsParsingService keyValuePairsParsingService
            )
        {
            _prismNavigationConfiguration = prismNavigationConfiguration;
            _regionManager = regionManager;
            _viewCreationService = viewCreationService;
            _viewToUrlMappingService = viewToUrlMappingService;
            _viewNavigationInformationParsingService = viewNavigationInformationParsingService;
            _moduleLoadingService = moduleLoadingService;
            _moduleNamesRegistrationService = moduleNamesRegistrationService;
            _viewInitializationService = viewInitializationService;
            _keyValuePairsParsingService = keyValuePairsParsingService;

            this._moduleLoadingService.ModuleDownloadStateChanged += this.ModuleDownloadStateChanged;
        }

        private void ModuleDownloadStateChanged(object sender, ModuleDownloadStateChangedEventArgs e)
        {
            if (e.ModuleDownloadStatus != ModuleDownloadStatus.DownloadCompleted)
            {
                // we want to know only if module is finished downloading
                return;
            }

            if (e.Error != null)
            {
                // if there is error, then Prism will throw exception
                return;
            }

            var thereAreViewsToLoadForThisModule =
                _queueOfViewsToDownloadWhenTheirModuleIsDownloaded.Where(
                    p => p.ModuleName.ToLower() == e.ModuleName.ToLower()).Any();

            var viewsToRequeue = new Queue<ModuleViewLoadingInformation>();

            if (thereAreViewsToLoadForThisModule)
            {               
                while(_queueOfViewsToDownloadWhenTheirModuleIsDownloaded.Any())
                {
                    var nextViewToLoad = _queueOfViewsToDownloadWhenTheirModuleIsDownloaded.Dequeue();

                    var itsViewForThisModule = (nextViewToLoad.ModuleName.ToLower() == e.ModuleName.ToLower());

                    if (itsViewForThisModule)
                    {
                        this.LoadViewToRegion(nextViewToLoad.RegionName, nextViewToLoad.ViewName, nextViewToLoad.Path);
                    }
                    else
                    {
                        viewsToRequeue.Enqueue(nextViewToLoad);
                    }
                }

                if (viewsToRequeue.Any())
                {
                    foreach (var moduleViewLoadingInformation in viewsToRequeue)
                    {
                        _queueOfViewsToDownloadWhenTheirModuleIsDownloaded.Enqueue(moduleViewLoadingInformation);                        
                    }                  
                }
            }
        }


        public bool UrlNeedsToBeLoadedToMainRegion(string url)
        {
            if (string.IsNullOrEmpty(url))
            {
                return false;
            }

            if (string.IsNullOrEmpty(this.CurrentMainRegionUrl))
            {
                return true;
            }

            return this.CurrentMainRegionUrl.ToLower() != url.ToLower();
        }

        private void QueueViewToBeDownloadedWhenItsModuleIsLoaded(ModuleViewLoadingInformation viewLoadingInfo)
        {
            if (viewLoadingInfo == null)
            {
                return;
            }

            if (_queueOfViewsToDownloadWhenTheirModuleIsDownloaded.Contains(viewLoadingInfo))
            {
                return;
            }

            this._queueOfViewsToDownloadWhenTheirModuleIsDownloaded.Enqueue(viewLoadingInfo);
        }

        public void LoadUrlToRegion(string path, string regionName)
        {
            if (!string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(regionName))
            {
                var viewNavigationInfo = this._viewNavigationInformationParsingService.ParsePath(path);

                var realModuleName =
                    this._moduleNamesRegistrationService.GetRealModuleName(viewNavigationInfo.ModuleName);

                if (_moduleLoadingService.ModuleNeedsToBeLoaded(realModuleName))
                {                 
                    this.QueueViewToBeDownloadedWhenItsModuleIsLoaded(new ModuleViewLoadingInformation()
                                                                          {
                                                                              ModuleName = realModuleName, 
                                                                              ModuleUrlFriendlyName = viewNavigationInfo.ModuleName,
                                                                              ViewName = viewNavigationInfo.ViewName, 
                                                                              RegionName = regionName, 
                                                                              Path = path
                                                                          });
                    this._moduleLoadingService.LoadModule(realModuleName);
                    return;
                }

                if (viewNavigationInfo.ViewNameIsNotEmpty)
                {
                    LoadViewToRegion(regionName, viewNavigationInfo.ViewName, path);
                }
            }
        }

        public void LoadViewToRegion(Type viewType, string regionName)
        {
            this.LoadViewToRegion(viewType, regionName, string.Empty);
        }

        public void LoadViewToRegion(Type viewType, ViewInitializationData initializationData)
        {
            var view = this._viewCreationService.CreateView(viewType);
            var initializedView = this._viewInitializationService.InitializeView(view, initializationData);

            this.LoadViewToRegion(initializationData.RegionName, initializedView);
            this.UpdateApplicationNavigationContextIfViewWasLoadedToMainRegion(initializedView, initializationData);
        }

        public void LoadViewToRegion(Type viewType, string regionName, string path)
        {
            var view = this._viewCreationService.CreateView(viewType);
            var initializedView = this._viewInitializationService.InitializeView(view, regionName, path);

            this.LoadViewToRegion(regionName, initializedView);
            this.UpdateApplicationNavigationContextIfViewWasLoadedToMainRegion(initializedView, new ViewInitializationData() {RegionName=regionName});
        }

        public void LoadViewToRegion<TViewType>(string regionName)
        {
            this.LoadViewToRegion(typeof(TViewType), regionName, string.Empty);
        }

        public void LoadViewToRegion<TViewType>(ViewInitializationData initializationData)
        {
            this.LoadViewToRegion(typeof(TViewType), initializationData);
        }

        private void LoadViewToRegion(string regionName, object view)
        {
            if (view != null)
            {
                this._regionManager.AddToRegion(regionName, view);
                this._regionManager.Regions[regionName].Activate(view);
            }
            else
            {
                throw new Exception("Could not show null view to region" + regionName);
            }            
        }

        private void LoadViewToRegion(string regionName, string viewName, string path)
        {
            if (!string.IsNullOrEmpty(viewName) && !string.IsNullOrEmpty(regionName))
            {
                var view = this._viewCreationService.CreateView(viewName);
                var initializedView = this._viewInitializationService.InitializeView(view, regionName, path);

                if (view != null)
                {
                    this.LoadViewToRegion(regionName, initializedView);
                    this.UpdateApplicationNavigationContextIfViewWasLoadedToMainRegion(regionName, path);
                }
                else
                {
                    throw new Exception("Could not find view " + viewName);                    
                }
            }
        }



        public void UnloadViewFromRegion(string regionName, object view)
        {
            if (!this._regionManager.Regions.ContainsRegionWithName(regionName))
            {
                throw new InvalidOperationException(string.Format("Cannot unload view because region '{0}' does not exist.", regionName));
            }

            this._regionManager.Regions[regionName].Remove(view);
        }

        private void UpdateApplicationNavigationContextIfViewWasLoadedToMainRegion(object view, ViewInitializationData initializationData)
        {
            if (initializationData.RegionName.ToLower() == this._prismNavigationConfiguration.MainRegionName.ToLower())
            {
                var viewUrl = this._viewToUrlMappingService.GetViewUrl(view.GetType());
                if (this.CurrentMainRegionUrl.ToLower() != viewUrl.ToLower())
                {
                    if (!viewUrl.EndsWith(this._keyValuePairsParsingService.Separator))
                    {
                        viewUrl = viewUrl + this._keyValuePairsParsingService.Separator;
                    }

                    viewUrl = viewUrl + this._keyValuePairsParsingService.SerializeKeysAndValues(initializationData.Parameters);

                    this.CurrentMainRegionUrl = viewUrl;
                    Application.Current.Host.NavigationState = viewUrl;
                }
            }
        }

        private void UpdateApplicationNavigationContextIfViewWasLoadedToMainRegion(string regionName, string newUrl)
        {
            if (regionName.ToLower() == this._prismNavigationConfiguration.MainRegionName.ToLower())
            {
                if (this.CurrentMainRegionUrl.ToLower() != newUrl.ToLower())
                {
                    this.CurrentMainRegionUrl = newUrl;
                    Application.Current.Host.NavigationState = newUrl;
                }
            }
        }
    }
}